package orbitPrototype.objects;

import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import orbitPrototype.Util;

/**
 * Description:	Contains a collection of DisplayObject's and manages their drawing order in a
 * 				glorified arraylist. Index 0 is drawn by the renderer LAST. A DisplayObjectContainer
 * 				is itself NEVER drawn on screen, only the subobjects attached to it.
 * 
 * 				For now, area of DisplayObjectContainer is abstract and does not necessarily
 * 				represent the area covered by its subobjects. The DisplayObjectContainer area 
 * 				does however need to cover any subobjects if the intent is to use functions such as 
 * 				getDisplayObjectsUnderPoint, as they rely on DisplayObjectContainer having an area.
 * 
 * @author Drapan
 *
 */
public class DisplayObjectContainer extends DisplayObject
{
	private ArrayList<DisplayObject> _displayObjects = null;
	
	private static final int DEFAULT_INSERT_INDEX = 0;
	
	public DisplayObjectContainer(int width, int height)
	{
		super(0, 0, width, height, DisplayObject.TYPE_CONTAINER);
		
		_displayObjects = new ArrayList<DisplayObject>();
	}
	
	public DisplayObjectContainer(int x, int y, int width, int height)
	{
		super(x, y, width, height, DisplayObject.TYPE_CONTAINER);
		
		_displayObjects = new ArrayList<DisplayObject>();
	}
	
	/*
	 * FUNCTIONS
	 */
	
	/**
	 * Description: Adds DisplayObject to this container. 
	 * @param displayObject
	 */
	public void addChild(DisplayObject displayObject)
	{
		addChild(displayObject, DEFAULT_INSERT_INDEX);
	}
	
	/**
	 * Description: Adds DisplayObject to this container at specific index.
	 * @param displayObject
	 * @param index
	 */
	public void addChild(DisplayObject displayObject, int index)
	{
		if(displayObject != null)
		{
			if(!_displayObjects.contains(displayObject))
			{
				if(displayObject != this)
				{
					if(!isValidIndex(index))
					{
						_displayObjects.add(displayObject);
					}
					else
					{
						_displayObjects.add(index, displayObject);
					}
					
					displayObject.setParent(this);
				}
				else
				{
					Util.error("Can't add DisplayObjectContainer to itself.");
				}
			}
			else
			{
				Util.error("DisplayObject already in list.");
			}
		}
		else
		{
			Util.error("Can't add null displayObject to container");
		}
	}
	
	/**
	 * Description: Removes specified DisplayObject from this container.
	 * @param displayObject
	 */
	public void removeChild(DisplayObject displayObject)
	{
		int indexOf = _displayObjects.indexOf(displayObject);
		
		if(indexOf != -1)
		{
			_displayObjects.remove(indexOf);
			displayObject.setParent(null);
		}
		else
		{
			Util.error("DisplayObject is not present in list");
		}
	}
	
	/**
	 * Description: Moves specified DisplayObject to front.
	 * @param displayObject
	 */
	public void moveToFront(DisplayObject displayObject)
	{
		int indexOf = _displayObjects.indexOf(displayObject);
		
		if(indexOf != -1)
		{
			_displayObjects.remove(indexOf);
			_displayObjects.add(DEFAULT_INSERT_INDEX, displayObject);
		}
		else
		{
			Util.error("DisplayObject is not present in list");
		}
	}
	
	/**
	 * Description: Moves specified DisplayObject to back.
	 * @param displayObject
	 */
	public void moveToBack(DisplayObject displayObject)
	{
		int indexOf = _displayObjects.indexOf(displayObject);
		
		if(indexOf != -1)
		{
			_displayObjects.remove(indexOf);
			_displayObjects.add(displayObject);
		}
		else
		{
			Util.error("DisplayObject is not present in list");
		}
	}
	
	/**
	 * Description: Swaps Z-index of the two specified DisplayObjects.
	 * @param displayObjectA
	 * @param displayObjectB
	 */
	public void swapZ(DisplayObject displayObjectA, DisplayObject displayObjectB)
	{
		int indexOfA = _displayObjects.indexOf(displayObjectA);
		int indexOfB = _displayObjects.indexOf(displayObjectB);
		
		if(indexOfA != -1 && indexOfB != -1)
		{
			_displayObjects.remove(indexOfA);
			_displayObjects.add(indexOfA, displayObjectB);
			
			_displayObjects.remove(indexOfB);
			_displayObjects.add(indexOfB, displayObjectA);
		}
		else
		{
			Util.error("Atleast one DisplayObject is not present in list");
		}
	}
	
	/**
	 * Description: Moves first specified DisplayObject infront of second specified DisplayObject.
	 * @param displayObject
	 * @param displayObjectVictim
	 */
	public void moveInfrontOf(DisplayObject displayObject, DisplayObject displayObjectVictim)
	{
		int indexOf = _displayObjects.indexOf(displayObject);
		int indexOfVictim = _displayObjects.indexOf(displayObjectVictim);
		
		if(indexOf != -1 && indexOfVictim != -1)
		{
			if(indexOfVictim < indexOf)
			{
				_displayObjects.remove(indexOf);
				_displayObjects.add(indexOfVictim, displayObject);
			}
		}
		else
		{
			Util.error("DisplayObject is not present in list");
		}
	}
	
	/**
	 * Description:	Hides all other DisplayObjects except the one specified. 
	 * @param displayObject
	 */
	public void makeOnlyVisible(DisplayObject displayObject)
	{
		int indexOf = _displayObjects.indexOf(displayObject);
		
		if(indexOf != -1)
		{
			displayObject.setVisible(true);
			
			for(DisplayObject currentDisplayObject:_displayObjects)
			{
				if(displayObject != currentDisplayObject)
				{
					currentDisplayObject.setVisible(false);
				}
			}
		}
		else
		{
			Util.error("Atleast one DisplayObject is not present in list");
		}
	}
	
	/**
	 * Description:	Makes all subobjects visible. Does not make contents of subcontainer visible.
	 */
	public void makeAllVisible()
	{
		for(DisplayObject currentDisplayObject:_displayObjects)
		{
			currentDisplayObject.setVisible(true);
		}
	}
	
	/**
	 * Description:	Shifts all of the contents within this container by specified x and y distance,
	 * 				without moving the container itself.
	 * @param x
	 * @param y
	 */
	public void shift(int x, int y)
	{
		for(DisplayObject displayObject:_displayObjects)
		{
			displayObject.setX(displayObject.getX() + x);
			displayObject.setY(displayObject.getY() + y);	
		}
	}
		
	/**
	 * Description: Removes all DisplayObjects from this container.
	 * 
	 */
	
	// TODO Needs refinment to allow for removing all subobjects from memory, not just from list.
	public void clear()
	{
		
		_displayObjects.clear();
	}
	
	/*
	 * HELPER FUNCTIONS
	 */
	
	/**
	 * Description: Checks if specified Z-index is valid for this container.
	 * @param index
	 * @return boolean
	 */
	private boolean isValidIndex(int index)
	{
		if(index > _displayObjects.size() - 1)
		{
			return false;
		}
		else if(index < 0)
		{
			return false;
		}
		else
		{
			return true;
		}
	}
	
//	private void checkAdjustSizeOnAdd(DisplayObject displayObject)
//	{
//		if(displayObject.getX() + displayObject.getWidth() > this.getWidth())
//		{
//			this.setWidth(this.getWidth() + (displayObject.getX() + displayObject.getWidth() - this.getWidth()));
//		}
//		
//		if(displayObject.getY() + displayObject.getHeight() > this.getHeight())
//		{
//			this.setHeight(this.getHeight() + (displayObject.getY() + displayObject.getHeight() - this.getHeight()));
//		}
//	}
	
	
	
	/*
	 * SETTERS
	 */
	
	
	/*
	 * GETTERS
	 */
	
	/**
	 * Description: Returns DisplayObject at specified Z-index. 
	 * @param index
	 * @return DisplayObject
	 */
	public DisplayObject getChildAt(int index)
	{
		if(this.isValidIndex(index))
		{
			return _displayObjects.get(index);
		}
		else
		{
			return null;
		}
	}
	
	/**
	 * Description: Returns Z-index of specified DisplayObject.
	 * @param displayObject
	 * @return int
	 */
	public int getChildIndex(DisplayObject displayObject)
	{
		int indexOf = _displayObjects.indexOf(displayObject);
		
		if(indexOf != -1)
		{
			return indexOf;
		}
		else
		{
			Util.error("DisplayObject is not present in list");
			return indexOf;
		}
	}
	
	public ArrayList<DisplayObject> getDisplayObjectsUnderPoint(Point point)
	{
		return this.getDisplayObjectsUnderPoint(point.x, point.y);
	}
	
	public ArrayList<DisplayObject> getDisplayObjectsUnderPoint(int x, int y)
	{
		return getDisplayObjectsUnderPoint(x, y, true);
	}
	
	/**
	 * Description: Gets all DisplayObjects under specified point. Also includes DisplayObjectContainers and subobjects.
	 * 				Return array is sorted from top to bottom.
	 * @param x
	 * @param y
	 * @return ArrayList<DisplayObject>
	 */
	public ArrayList<DisplayObject> getDisplayObjectsUnderPoint(int x, int y, boolean visibleOnly)
	{
		ArrayList<DisplayObject> returnList = new ArrayList<DisplayObject>();
		
		for(DisplayObject displayObject:_displayObjects)
		{
			if(visibleOnly && !displayObject.getVisible())
			{
				continue;
			}
			
			if(x > (displayObject.getX() + this.getX()) && x < (displayObject.getX() + this.getX()) + displayObject.getWidth() && 
				y > (displayObject.getY() + this.getY()) && y < (displayObject.getY() + this.getY()) + displayObject.getHeight())
			{				
				if(displayObject.getGraphicsType() == DisplayObject.TYPE_CONTAINER)
				{
					returnList.add(displayObject);
					returnList.addAll(((DisplayObjectContainer) displayObject).getDisplayObjectsUnderPoint(x - this.getX(), y - this.getY()));
				}
				else
				{
					returnList.add(displayObject);
				}
			}
		}
		
		return returnList;
	}
	
	/**
	 * Description:	Returns a rectangle which represents bounding box of the DisplayObjectContainer
	 * @return Rectangle
	 */
	public Rectangle getBoundingBox()
	{	
		Point p1 = null;
		Point p2 = null;
				
		for (DisplayObject displayObject : _displayObjects)
		{
			if(p1 == null && p2 == null)
			{
				p1 = new Point(displayObject.getX(), displayObject.getY());
				p2 = new Point(displayObject.getX() + displayObject.getWidth(), displayObject.getY() + displayObject.getHeight());
			}
			else
			{
				if(displayObject.getX() < p1.x)
				{
					p1.x = displayObject.getX();
				}
				if(displayObject.getY() < p1.y)
				{
					p1.y = displayObject.getY();
				}
				if(displayObject.getX() + displayObject.getWidth() > p2.x)
				{
					p2.x = displayObject.getX() + displayObject.getWidth();
				}
				if(displayObject.getY() + displayObject.getHeight() > p2.y)
				{
					p2.y = displayObject.getY() + displayObject.getHeight();
				}
			}
		}
		
		return new Rectangle(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
	}
	
	public ArrayList<DisplayObject> getDisplayObjectList()
	{
		return _displayObjects;
	}
	
	public int getNumChildren()
	{
		return _displayObjects.size();
	}
	
}
